home *** CD-ROM | disk | FTP | other *** search
/ Aminet 2 / Aminet AMIGA CDROM (1994)(Walnut Creek)[Feb 1994][W.O. 44790-1].iso / Aminet / mus / play / tracker_3_19.lzh / tracker / amiga_support.c < prev    next >
C/C++ Source or Header  |  1993-11-17  |  15KB  |  762 lines

  1. /* support functions for yours truly amiga */
  2.  
  3. /* $Id: amiga_support.c,v 1.2 1993/11/17 15:29:53 espie Exp espie $ */
  4. /* $Log: amiga_support.c,v $
  5.  * Revision 1.2  1993/11/17  15:29:53  espie
  6.  * Added higher-level support. Separated plainly from audio.c
  7.  * */
  8.  
  9. #include <stdio.h>
  10. #include "defs.h"
  11. #include "extern.h"
  12.  
  13. #include "song.h"
  14. #include "channel.h"
  15.  
  16. #include <hardware/cia.h>
  17. #include <hardware/intbits.h>
  18. #include <hardware/dmabits.h>
  19. #include <exec/nodes.h>
  20. #include <exec/memory.h>
  21. #include <dos/dos.h>
  22. #include <devices/audio.h>
  23. #include <devices/timer.h>
  24. #include <proto/exec.h>
  25. #include <proto/dos.h>
  26. #include <proto/timer.h>
  27. #undef X
  28. #include <proto/intuition.h>
  29. #include <proto/gadtools.h>
  30.  
  31. LOCAL struct audio_channel
  32.     {
  33.     int amiga_number;
  34.     struct sample_info *samp;
  35.     int volume;
  36.     int pitch;
  37.     } chan[4];
  38.  
  39. LOCAL struct sample_info dummy =
  40.     {
  41.     NULL,
  42.     0,
  43.     0,
  44.     0,
  45.     0,
  46.     0,
  47.     0,
  48.     0,
  49.     NULL,
  50.     NULL
  51.     };
  52.  
  53. LOCAL int allocated = 0;
  54.  
  55. struct audio_channel *new_channel()
  56.     {
  57.     struct audio_channel *new;
  58.  
  59.     new = &chan[allocated];
  60.     new->amiga_number = allocated++;
  61.     new->samp = &dummy;
  62.     new->volume = 0;
  63.     new->pitch = 0;
  64.     new->samp = 0;
  65.     return new;
  66.     }
  67.  
  68. void no_audio_channels()
  69.     {
  70.     allocated = 0;
  71.     }
  72.  
  73. /* list scanning.
  74.  * next is needed: with it, we can actually unlink the node while scanning
  75.  *    the current list.  Type is provided to avoid type-casting errors in
  76.  * SCANLIST expansion.
  77.  */
  78. #define SCANLIST(node, next, list, type) \
  79.     for((node) = (type)((struct MinList *)(list))->mlh_Head; \
  80.         (next) = (type)((struct MinNode *)(node))->mln_Succ; \
  81.         (node) = (next))
  82.  
  83. LOCAL char *version = "\0$VER: tracker 3.11";
  84.  
  85. /* remember allocated samples for cleaning up in panic case */
  86. LOCAL struct MinList tracked_list;
  87. /* timer variables:
  88.  *        the message port 
  89.  */
  90. LOCAL struct MsgPort *tport = 0;
  91. /*        the basic wait request */
  92. LOCAL struct timerequest *tr = 0;
  93. /*         the reference system time at which the song started */
  94. LOCAL struct EClockVal system_time;
  95.  
  96. LOCAL struct Library *TimerBase;
  97.  
  98. /* Clean up: Do we need to close timer ? */
  99. LOCAL BOOL timer_opened = FALSE;
  100. /* Not really useful */
  101. LOCAL BOOL first_time = FALSE;
  102. extern volatile struct CIA __far ciaa;
  103.  
  104. LOCAL void init_timer()
  105.     {
  106.     int fail;
  107.     tport = CreateMsgPort();
  108.     if (!tport)
  109.         {
  110.         exit(10);
  111.         }
  112.     tr = CreateIORequest(tport, sizeof(struct timerequest));
  113.     if (!tr)
  114.         {
  115.         exit(10);
  116.         }
  117.     fail = OpenDevice(TIMERNAME, UNIT_WAITECLOCK, tr, 0);
  118.     if (fail)
  119.         exit(10);
  120.     else
  121.         timer_opened = TRUE;
  122.     TimerBase = tr->tr_node.io_Device;
  123.     }
  124.  
  125. /* The basic user interface (a simple window) */
  126. LOCAL struct IntuitionBase *IntuitionBase = 0;
  127. LOCAL struct Library *GadtoolsBase = 0;
  128. LOCAL struct Window *win;
  129. /* We use topaz.font so as not to get into sizing problems */
  130. LOCAL struct TextAttr topaz =
  131.     {
  132.     "topaz.font",
  133.     8,
  134.     0,
  135.     0
  136.     };
  137.  
  138. LOCAL struct NewGadget template =
  139.     {
  140.     10, 10,     /* start position (offset from left/topedge)
  141.     72, 20,        /* width/height */
  142.     NULL,
  143.     &topaz,
  144.     0,            /* gadget ID */
  145.     0,
  146.     NULL,
  147.     NULL
  148.     };
  149.  
  150. LOCAL APTR vi;
  151. LOCAL struct Screen *pub = 0;
  152. LOCAL struct Gadget *glist;
  153.  
  154. /* we precisely have seven gadgets */
  155. #define MAX_GADGET 7
  156.  
  157. /* all labelled with strings */
  158. LOCAL char *label[MAX_GADGET] =
  159.     {
  160.     "Previous",
  161.     "Next",
  162.     "Restart",
  163.     "<<",
  164.     ">>", 
  165.     "NTSC",
  166.     "PAL",
  167.     };
  168.  
  169. /* and mapping to these commands of the normal tracker interface */
  170. LOCAL char mapto[MAX_GADGET] = { 'p', 'n', 'r', '<', '>', 'S', 's' };
  171.     
  172. LOCAL void init_ui(void)
  173.     {
  174.     struct Gadget *gad;
  175.     int i;
  176.  
  177.     IntuitionBase = OpenLibrary("intuition.library", 37);
  178.     if (!IntuitionBase)
  179.         exit(10);
  180.     GadtoolsBase = OpenLibrary("gadtools.library", 37);
  181.     if (!GadtoolsBase)
  182.         exit(10);
  183.     pub = LockPubScreen(NULL);
  184.     if (!pub)
  185.         exit(10);
  186.     vi = GetVisualInfo(pub, TAG_END);
  187.     if (!vi)
  188.         exit(10);
  189.     gad = CreateContext(&glist);
  190.     if (!gad)
  191.         exit(10);
  192.     template.ng_VisualInfo = vi;
  193.         /* set up Top/Left Edge of initial gadget according to Wbar */
  194.     template.ng_TopEdge += pub->WBorTop + pub->Font->ta_YSize + 1;
  195.         /* lay out gadgets */
  196.     for (i = 0; i < MAX_GADGET; i++)
  197.         {
  198.         template.ng_GadgetText = label[i];
  199.         gad = CreateGadget(BUTTON_KIND, gad, &template, TAG_END);
  200.         if (!gad)
  201.             exit(10);
  202.         template.ng_LeftEdge += template.ng_Width + 5;
  203.         template.ng_GadgetID++;
  204.         }
  205.         
  206.     win = OpenWindowTags(NULL, 
  207.         WA_Title, "Experiment IV",
  208.         WA_Width, template.ng_LeftEdge + 10,    
  209.         WA_Height, template.ng_Height + template.ng_TopEdge + 10,
  210.         WA_MouseQueue, 35,    /* we can't always answer messages */
  211.         WA_DepthGadget, TRUE,
  212.         WA_CloseGadget, TRUE,
  213.         WA_DragBar, TRUE,
  214.         WA_SmartRefresh, TRUE,    /* don't want to be bothered with refresh */
  215.         WA_Gadgets, glist,
  216.         WA_IDCMP, IDCMP_CLOSEWINDOW | BUTTONIDCMP | IDCMP_REFRESHWINDOW,
  217.         TAG_DONE, 0);
  218.     if (!win)
  219.         exit(10);
  220.     GT_RefreshWindow(win, NULL);    
  221.     }
  222.  
  223.  
  224. /* the audio interface */
  225.  
  226. /* we allow for lots more audio requests than we need */
  227. #define QUEUE_LENGTH 50
  228.  
  229. LOCAL struct ext_audio 
  230.     {
  231.     struct IOAudio request;
  232.     struct ext_audio *next;
  233.     } 
  234. /* the audio queue */    
  235.     *first = 0, 
  236. /* the specific request that get used for closing/opening */
  237.     *req = 0;
  238.  
  239. LOCAL struct MsgPort *port = 0;
  240. /* in order to clean up afterwards: */
  241. LOCAL BOOL audio_opened = FALSE;
  242.  
  243. LOCAL void command_audio(struct ext_audio *io);
  244. LOCAL void send_immediate(ULONG command, int mask);
  245.  
  246. LOCAL UBYTE whichchannel[] = {15};
  247.  
  248. /* allocate_channels(): sends a request to the audio.device for
  249.  * the channels.
  250.  */
  251. LOCAL void allocate_channels(void)
  252.    { 
  253.    struct ext_audio *sweep;
  254.  
  255.    req->request.ioa_Request.io_Command = ADCMD_ALLOCATE;
  256.    req->request.ioa_AllocKey = 0;
  257.    req->request.ioa_Data = whichchannel;
  258.    req->request.ioa_Length = sizeof(whichchannel);
  259.    command_audio(req);
  260.  
  261.    for (sweep = first; sweep; sweep = sweep->next)
  262.       sweep->request.ioa_AllocKey = req->request.ioa_AllocKey;
  263.    }
  264.     
  265. /* free_channels(): give back the channels.
  266.  */ 
  267. LOCAL void free_channels(void)
  268.    {
  269.    req->request.ioa_Request.io_Command = ADCMD_FREE;
  270.    command_audio(req);
  271.    }
  272.  
  273.  
  274. LOCAL struct ext_audio *create_request(void)
  275.    {
  276.    struct ext_audio *new;
  277.  
  278.    new = AllocMem(sizeof(struct ext_audio), MEMF_CLEAR|MEMF_PUBLIC);
  279.    if (req)
  280.       *new = *req;
  281.    return new;
  282.    }
  283.  
  284. /* creates a whole queue of audio requests */   
  285. LOCAL void create_queue(void)
  286.    {
  287.    struct ext_audio *last, *new;
  288.    int i;
  289.    
  290.    last = NULL;
  291.    for (i = 0; i < QUEUE_LENGTH; i++)
  292.       {
  293.       new = create_request();
  294.       new->next = last;
  295.       last = new;
  296.       }
  297.    first = new;
  298.    }
  299.  
  300. LOCAL void get_requests(void)
  301.    {
  302.    struct ext_audio *back;
  303.  
  304.    while(back = GetMsg(port))
  305.        if (back != req)        /* only those belonging to the queue */
  306.           {
  307.           back->next = first;
  308.           first = back;
  309.           }
  310.    }
  311.  
  312. LOCAL void send_immediate(ULONG command, int mask)
  313.    {
  314.    first->request.ioa_Request.io_Command = command;
  315.    first->request.ioa_Request.io_Flags = IOF_QUICK;
  316.    first->request.ioa_Request.io_Unit = mask;
  317.    BeginIO(first);
  318.    }
  319.  
  320. LOCAL void command_audio(struct ext_audio *io)
  321.    {
  322.    struct ext_audio *request;
  323.  
  324.    BeginIO(io);
  325.    WaitPort(port);
  326.     get_requests();
  327.    }
  328.  
  329. LOCAL void reset_audio(void)
  330.    {
  331.    send_immediate(CMD_RESET, 15);
  332.    get_requests();
  333.    }
  334.  
  335. /* obtain_audio(): allocate all the structures we will need to
  336.  * play with the audio device
  337.  */ 
  338. LOCAL void obtain_audio(void)
  339.    {
  340.    BYTE fail;
  341.    
  342.    port = CreateMsgPort();
  343.    if (!port)
  344.        {
  345.        fprintf(stderr, "Couldn't allocate port");
  346.         exit(10);
  347.         }
  348.    req = CreateIORequest(port, sizeof(struct ext_audio));
  349.    if (!req)
  350.        {
  351.        fprintf(stderr, "Couldn't allocate io request");
  352.        exit(10);
  353.         }
  354.    req->request.ioa_Request.io_Message.mn_Node.ln_Pri = 0;
  355.       /* Note that OpenDevice returns 0 on success
  356.        */
  357.    fail = OpenDevice("audio.device", 0L, (struct IORequest *)req, 0L);
  358.    if (fail)
  359.         {
  360.         fprintf(stderr, "Couldn't open audio device");
  361.         exit(10);
  362.         }
  363.     else
  364.         audio_opened = TRUE;
  365.    }
  366.  
  367. /* remember old task priority at exit (CLI process runs on the CLI context,
  368.  * and reverts to being a shell with the same priority on exit
  369.  */
  370. LOCAL int oldpri;
  371.  
  372. LOCAL void amiga_init()
  373.     {
  374.     oldpri = SetTaskPri(FindTask(0), 15);
  375.         /* make cursor invisible, speed up output some */
  376.     printf("\233""0 p\n");
  377.     obtain_audio();
  378.     create_queue();
  379.     allocate_channels();
  380.     init_timer();
  381.         /* audio filter OFF */
  382.     ciaa.ciapra |= CIAF_LED;
  383.     init_ui();
  384.     }
  385.  
  386. LOCAL void amiga_cleanup()
  387.     {
  388.     struct MinNode *current, *next;
  389.     
  390.     SetTaskPri(FindTask(0), oldpri);
  391.     printf("\233"" p\n");
  392.     if (win)
  393.         CloseWindow(win);
  394.     if (glist)
  395.         FreeGadgets(glist);
  396.     if (vi)
  397.         FreeVisualInfo(vi);
  398.     if (pub)
  399.         UnlockPubScreen(NULL, pub);
  400.     if (IntuitionBase)
  401.         CloseLibrary(IntuitionBase);
  402.     if (GadtoolsBase)
  403.         CloseLibrary(GadtoolsBase);
  404.     SCANLIST(current, next, &tracked_list, struct MinNode *)
  405.         FreeVec(current);
  406.    if (req->request.ioa_AllocKey)
  407.         free_channels();
  408.     if (audio_opened)
  409.         CloseDevice(req);
  410.     if (req)
  411.         DeleteIORequest(req);
  412.     if (port)
  413.         DeletePort(port);
  414.     while(first)
  415.         {
  416.         struct ext_audio *temp;
  417.         
  418.         temp = first;
  419.         first = first->next;
  420.         FreeMem(temp, sizeof(struct ext_audio));
  421.         }
  422.  
  423.     if (timer_opened)
  424.         {
  425.         if (!CheckIO(tr))
  426.             {
  427.             AbortIO(tr);
  428.             WaitIO(tr);
  429.             }
  430.         CloseDevice(tr);
  431.         }
  432.     if (tr)
  433.         DeleteIORequest(tr);
  434.     if (tport)
  435.         DeleteMsgPort(tport);
  436.     ciaa.ciapra &= ~CIAF_LED;
  437.     }
  438.  
  439. /* indispensible for not losing memory ! */
  440.  
  441. void __regargs __chkabort()
  442.     {
  443.     }
  444.     
  445. void *alloc_sample(int len)
  446.     {
  447.     char *s;
  448.     
  449.     s = AllocVec(len + sizeof(struct MinNode), MEMF_CHIP | MEMF_CLEAR);
  450.     if (!s)
  451.         return 0;
  452.     AddTail(&tracked_list, s);
  453.     return s + sizeof(struct MinNode);
  454.     }
  455.  
  456. void free_sample(char *s)
  457.     {
  458.     s -= sizeof(struct MinNode);
  459.     Remove(s);
  460.     FreeVec(s);
  461.     }
  462.  
  463. /* termio */
  464. BOOL run_in_fg()
  465.     {
  466.     return 1;
  467.     }
  468.  
  469. void nonblocking_io()
  470.     {
  471.     static BOOL yet = 0;
  472.     if (!yet)
  473.         {
  474.         NewList(&tracked_list);
  475.         atexit(amiga_cleanup);
  476.         amiga_init();
  477.         yet = 1;
  478.         }
  479.     }
  480.  
  481. void sane_tty()
  482.     {
  483.     }
  484.     
  485. int may_getchar()
  486.     {
  487.     struct IntuiMessage *msg;
  488.     int id;
  489.     if (SetSignal(0,0) & SIGBREAKF_CTRL_C)
  490.         exit(10);
  491.     while(msg = GT_GetIMsg(win->UserPort))
  492.         switch(msg->Class)
  493.             {
  494.         case IDCMP_CLOSEWINDOW:
  495.             GT_ReplyIMsg(msg);
  496.             return 'q';
  497.         case IDCMP_REFRESHWINDOW:
  498.             GT_ReplyIMsg(msg);
  499.             GT_BeginRefresh(win);
  500.             GT_EndRefresh(win, TRUE);
  501.             break;
  502.         case IDCMP_GADGETUP:
  503.             id = ((struct Gadget *)msg->IAddress)->GadgetID;
  504.             GT_ReplyIMsg(msg);
  505.             return mapto[id];
  506.         default:
  507.             GT_ReplyIMsg(msg);
  508.             }
  509.     return -1;
  510.     }
  511.  
  512. /* audio */
  513.  
  514. /* empty operation on the amiga */
  515. void init_tables(oversample, frequency, chan)
  516. int oversample, frequency;
  517. struct channel *chan;
  518.     {
  519.     }
  520.  
  521. void resample(chan, oversample, number)
  522. struct channel *chan;
  523. int oversample;
  524. int number;
  525.     {
  526.     int i;
  527. /*
  528.     if (first_time)
  529.         {
  530.         first_time = FALSE;
  531.         system_time.tv_micro += number;
  532.         }
  533. */
  534.         tr->tr_node.io_Command = TR_ADDREQUEST;
  535.         tr->tr_time.tv_secs = system_time.ev_hi;
  536.         tr->tr_time.tv_micro = system_time.ev_lo;
  537.         SendIO(tr);
  538.         WaitPort(tport);
  539.         while(GetMsg(tport))
  540.             ;
  541.     system_time.ev_lo += (unsigned long)number;
  542.     if (system_time.ev_lo < (unsigned long)number)
  543.         system_time.ev_hi++;
  544.     }
  545.  
  546. void play_note(au, samp, pitch)
  547. struct audio_channel *au;
  548. struct sample_info *samp;
  549. int pitch;
  550.     {
  551.     au->pitch = pitch;
  552.     send_immediate(CMD_FLUSH, 1 << au->amiga_number);
  553.     get_requests();
  554.     if (samp)
  555.         {
  556.         au->samp = samp;
  557.         if (au->samp->start)
  558.             {
  559.             first->request.ioa_Request.io_Command = CMD_WRITE;
  560.             first->request.ioa_Request.io_Flags = ADIOF_PERVOL;
  561.             first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
  562.             first->request.ioa_Data = au->samp->start;
  563.             first->request.ioa_Length = au->samp->length;
  564.             first->request.ioa_Period = au->pitch;
  565.             first->request.ioa_Volume = au->volume;
  566.             first->request.ioa_Cycles = 1;
  567.             BeginIO(first);
  568.             first = first->next;
  569.             if (au->samp->rp_start)
  570.                 {
  571.                 first->request.ioa_Request.io_Command = CMD_WRITE;
  572.                 first->request.ioa_Request.io_Flags = 0;
  573.                 first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
  574.                 first->request.ioa_Data = au->samp->rp_start;
  575.                 first->request.ioa_Length = au->samp->rp_length;
  576.                 first->request.ioa_Cycles = 0;
  577.                 BeginIO(first);
  578.                 first = first->next;
  579.                 }  
  580.             }
  581.         }
  582.     }
  583.  
  584. void set_play_pitch(au, pitch)
  585. struct audio_channel *au;
  586. int pitch;
  587.     {
  588.     if (pitch != au->pitch)
  589.         {
  590.         au->pitch = pitch;
  591.         first->request.ioa_Period = pitch;
  592.         first->request.ioa_Volume = au->volume;
  593.         send_immediate(ADCMD_PERVOL, 1 << au->amiga_number);
  594.         }
  595.     }
  596.  
  597. void set_play_volume(au, volume)
  598. struct audio_channel *au;
  599. int volume;
  600.     {
  601.     if (volume != au->volume)
  602.         {
  603.         au->volume = volume;
  604.         first->request.ioa_Period = au->pitch;
  605.         first->request.ioa_Volume = volume;
  606.         send_immediate(ADCMD_PERVOL, 1 << au->amiga_number);
  607.         }
  608.     }
  609.  
  610. void set_position(au, pos)
  611. struct audio_channel *au;
  612. int pos;
  613.     {
  614.     send_immediate(CMD_FLUSH, 1 << au->amiga_number);
  615.     get_requests();
  616.     if (au->samp->start)
  617.         {
  618.         if (pos < au->samp->length)
  619.             {
  620.             first->request.ioa_Request.io_Command = CMD_WRITE;
  621.             first->request.ioa_Request.io_Flags = ADIOF_PERVOL;
  622.             first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
  623.             first->request.ioa_Data = au->samp->start + pos;
  624.             first->request.ioa_Length = au->samp->length - pos;
  625.             first->request.ioa_Period = au->pitch;
  626.             first->request.ioa_Volume = au->volume;
  627.             first->request.ioa_Cycles = 1;
  628.             BeginIO(first);
  629.             first = first->next;
  630.             if (au->samp->rp_start)
  631.                 {
  632.                 first->request.ioa_Request.io_Command = CMD_WRITE;
  633.                 first->request.ioa_Request.io_Flags = 0;
  634.                 first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
  635.                 first->request.ioa_Data = au->samp->rp_start;
  636.                 first->request.ioa_Length = au->samp->rp_length;
  637.                 first->request.ioa_Cycles = 0;
  638.                 BeginIO(first);
  639.                 first = first->next;
  640.                 }  
  641.             }
  642.         }
  643.     }
  644.  
  645.  
  646. void set_mix(percent)
  647. int percent;
  648.     {
  649.     }
  650.  
  651. int open_audio(f, s)
  652. int f, s;
  653.     {
  654.     first_time = TRUE;
  655.     return ReadEClock(&system_time);        /* samples/sec used as a timing unit: 1sec =1 000 000 µs */
  656.     }
  657.     
  658. void set_synchro(s)
  659. BOOL s;
  660.     {
  661.     ReadEClock(&system_time);
  662.     first_time = TRUE;
  663.     }
  664.  
  665. int update_frequency()
  666.     {
  667.     return 0;
  668.     }
  669.  
  670. void output_samples(int left, int right)
  671.     {
  672.     }
  673.     
  674. void flush_buffer(void)
  675.     {
  676.     }
  677.  
  678. void discard_buffer(void)
  679.     {
  680.     reset_audio();
  681.     }
  682.     
  683. void close_audio(void)
  684.     {
  685.     }
  686.     
  687.  
  688.     
  689. /* amiga: popen() for OS2_04 */
  690.  
  691.  
  692. #include <proto/dos.h>
  693. #include <dos/dostags.h>
  694. #include <stdio.h>
  695. #include <string.h>
  696.  
  697. /*
  698. ###    CSupport/popen
  699. ###
  700. ###    NAME
  701. ###        popen/pclose -- Unix-like pipes
  702. ###
  703. ###    STATUS
  704. ###        Experimental
  705. ###
  706.  */
  707. FILE *popen(char *command, char *mode)
  708.     {
  709.     
  710.     if (strcmp(mode, "r") == 0)
  711.         /* open pipe for reading */
  712.         {
  713.         FILE *reader;
  714.         BPTR writer, null;
  715.  
  716.         writer = Open("PIPE:", MODE_NEWFILE);
  717.         reader = fopen("PIPE:", "r");
  718.         null = Open("NIL:", MODE_NEWFILE);
  719.         if (SystemTags(command, SYS_Input, null, 
  720.             SYS_Output, writer, SYS_Asynch, TRUE, TAG_END) == -1)
  721.             {
  722.             Close(null);
  723.             Close(writer);
  724.             fclose(reader);
  725.             return NULL;
  726.             }
  727.         else
  728.             return reader;
  729.         }
  730.     else if (strcmp(mode, "w") == 0)
  731.         /* open pipe for writing */
  732.         {
  733.         FILE *writer;
  734.         BPTR reader, null;
  735.         
  736.         writer = fopen("PIPE:", "w");
  737.         reader = Open("PIPE:", MODE_OLDFILE);
  738.         null = Open("NIL:", MODE_NEWFILE);
  739.         if (SystemTags(command, SYS_Input, reader, 
  740.             SYS_Output, null, SYS_Asynch, TRUE, TAG_END) == -1)
  741.             {
  742.             Close(null);
  743.             Close(reader);
  744.             fclose(writer);
  745.             return NULL;
  746.             }
  747.         else
  748.             return writer;
  749.         }
  750.     else
  751.         return NULL;
  752.     }
  753.  
  754. /* for us, pclose is just fclose.
  755.  * We hope everything will close out alright
  756.  */
  757. void pclose(FILE *f)
  758.     {
  759.     fclose(f);
  760.     }
  761.  
  762.